﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using MugenInjection.Delegates;
using MugenInjection.Interface;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    ///     Represents the reflection access provider which use System.Reflection.Emit to members access.
    /// </summary>
    public sealed class EmitReflectionAccessProvider : IReflectionAccessProvider
    {
        #region Nested type: WeakEventCacheItem

        private sealed class WeakEventCacheItem : IEquatable<WeakEventCacheItem>
        {
            #region Equality members

            /// <summary>
            ///     Indicates whether the current object is equal to another object of the same type.
            /// </summary>
            /// <returns>
            ///     true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
            /// </returns>
            /// <param name="other">An object to compare with this object.</param>
            public bool Equals(WeakEventCacheItem other)
            {
                return _delegateType == other._delegateType && Equals(_methodInfo, other._methodInfo) && _targetType == other._targetType;
            }

            /// <summary>
            ///     Indicates whether this instance and a specified object are equal.
            /// </summary>
            /// <returns>
            ///     true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false.
            /// </returns>
            /// <param name="obj">Another object to compare to. </param>
            /// <filterpriority>2</filterpriority>
            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                return obj is WeakEventCacheItem && Equals((WeakEventCacheItem)obj);
            }

            /// <summary>
            ///     Returns the hash code for this instance.
            /// </summary>
            /// <returns>
            ///     A 32-bit signed integer that is the hash code for this instance.
            /// </returns>
            /// <filterpriority>2</filterpriority>
            public override int GetHashCode()
            {
                unchecked
                {
                    return ((_delegateType != null ? _delegateType.GetHashCode() : 0) * 397) ^
                           (_methodInfo != null ? _methodInfo.GetHashCode() : 0) ^
                           (_targetType != null ? _targetType.GetHashCode() : 0);
                }
            }

            public static bool operator ==(WeakEventCacheItem left, WeakEventCacheItem right)
            {
                return left.Equals(right);
            }

            public static bool operator !=(WeakEventCacheItem left, WeakEventCacheItem right)
            {
                return !left.Equals(right);
            }

            #endregion

            #region Fields

            private readonly Type _delegateType;
            private readonly MethodInfo _methodInfo;
            private readonly Type _targetType;

            #endregion

            #region Constructor

            /// <summary>
            ///     Initializes a new instance of the <see cref="WeakEventCacheItem" /> class.
            /// </summary>
            public WeakEventCacheItem(Type delegateType, MethodInfo methodInfo, Type targetType)
            {
                _delegateType = delegateType;
                _methodInfo = methodInfo;
                _targetType = targetType;
            }

            #endregion
        }

        #endregion

        #region Fields

        private static readonly CacheCollection<ConstructorInfo, ActivatorDelegate> ConstructorActivators =
            new CacheCollection<ConstructorInfo, ActivatorDelegate>(30);

        private static readonly CacheCollection<MemberInfo, Delegate> MemberGetters =
            new CacheCollection<MemberInfo, Delegate>(30);

        private static readonly CacheCollection<MemberInfo, Delegate> MemberActivatorsRef =
            new CacheCollection<MemberInfo, Delegate>(30);

        private static readonly CacheCollection<MemberInfo, Delegate> MemberActivators =
            new CacheCollection<MemberInfo, Delegate>(30);

        private static readonly CacheCollection<MemberInfo, Delegate> MethodActivatorsRef =
            new CacheCollection<MemberInfo, Delegate>(30);

        private static readonly CacheCollection<MemberInfo, Delegate> MethodActivators =
            new CacheCollection<MemberInfo, Delegate>(30);

        private static readonly CacheCollection<Type, DynamicMethod> ProxyMethods =
            new CacheCollection<Type, DynamicMethod>(30);

        private static readonly CacheCollection<WeakEventCacheItem, DynamicMethod> WeakDelegates =
            new CacheCollection<WeakEventCacheItem, DynamicMethod>(30);

        #endregion

        #region Implementation of IReflectionAccessProvider

        /// <summary>
        ///     Gets a delegate to create an object.
        /// </summary>
        /// <param name="constructorInfo">
        ///     The specified <see cref="ConstructorInfo" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="Func{TResult,TValue}" />
        /// </returns>
        public ActivatorDelegate GetActivatorDelegate(ConstructorInfo constructorInfo)
        {
            Validate.ArgumentNotNull(constructorInfo, "constructorInfo");
            return GetActivatorDelegate(constructorInfo, true);
        }

        /// <summary>
        ///     Gets a delegate to set a value in the specified <see cref="MemberInfo" />.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="SetterDelegate{TTarget}" />
        /// </returns>
        public SetterDelegate<TTarget> GetAssignMemberDelegate<TTarget>(MemberInfo memberInfo) where TTarget : class
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            return GetAssignMemberDelegate<SetterDelegate<TTarget>, TTarget>(memberInfo, false, true);
        }

        /// <summary>
        ///     Gets a delegate to set specified value in the specified <see cref="MemberInfo" /> in a value type target, can be used with reference type.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="SetterDelegate{TTarget}" />
        /// </returns>
        public SetterValueTypeDelegate<TTarget> GetAssignValueMemberDelegate<TTarget>(MemberInfo memberInfo)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            return GetAssignMemberDelegate<SetterValueTypeDelegate<TTarget>, TTarget>(memberInfo, true, true);
        }

        /// <summary>
        ///     Gets a delegate to get a value in the specified <see cref="MemberInfo" />
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="T">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        public GetterDelegate<TTarget, T> GetGetterMemberDelegate<TTarget, T>(MemberInfo memberInfo)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            lock (MemberGetters)
            {
                var memberGetter = MemberGetters[memberInfo] as GetterDelegate<TTarget, T>;
                if (memberGetter == null)
                {
                    memberGetter =
                        (GetterDelegate<TTarget, T>)
                        CreateGetterMemberMethod<TTarget>(memberInfo, typeof(T))
                            .CreateDelegate(typeof(GetterDelegate<TTarget, T>));
                    MemberGetters[memberInfo] = memberGetter;
                }
                return memberGetter;
            }
        }

        /// <summary>
        ///     Gets a delegate to call specified <see cref="MethodInfo" />.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InvokeMethodDelegate{TTarget}" />
        /// </returns>
        public InvokeMethodDelegate<TTarget> GetInvokeMethodDelegate<TTarget>(MethodInfo methodInfo)
            where TTarget : class
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            return GetInvokeMethodDelegate<InvokeMethodDelegate<TTarget>, TTarget>(methodInfo, false, true);
        }

        /// <summary>
        ///     Gets a delegate to call specified <see cref="MethodInfo" /> in a value type target, can be used with reference type.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InvokeMethodDelegate{TTarget}" />
        /// </returns>
        public InvokeMethodValueTypeDelegate<TTarget> GetInvokeMethodValueTypeDelegate<TTarget>(MethodInfo methodInfo)
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            return GetInvokeMethodDelegate<InvokeMethodValueTypeDelegate<TTarget>, TTarget>(methodInfo, true, true);
        }

        /// <summary>
        ///     Converts the Func{object[], object} to the specified delegate type.
        /// </summary>
        /// <param name="proxyDelegate">The specified proxy method.</param>
        /// <param name="delegateType">The specified delegate type.</param>
        /// <returns>An instance of delegate type.</returns>
        public Delegate ConvertToProxyDelegate(Func<object[], object> proxyDelegate, Type delegateType)
        {
            Validate.ArgumentNotNull(proxyDelegate, "proxyDelegate");
            Validate.ArgumentNotNull(delegateType, "delegateType");
            Validate.IsAssignable(typeof(Delegate), delegateType);
            return ConvertToProxyDelegate(delegateType).CreateDelegate(delegateType, proxyDelegate);
        }

        /// <summary>
        ///     Returns a weak-reference version of a delegate.
        /// </summary>
        /// <param name="originalAction">The delegate to convert to weak referencing.</param>
        /// <param name="unsubcribeDelegate">The unregister action to invoke if the target is garbage collected.</param>
        /// <returns>A weak referencing delegate.</returns>
        public Delegate ConvertToWeakDelegate(Delegate originalAction, Action<Delegate> unsubcribeDelegate)
        {
            Validate.ArgumentNotNull(originalAction, "originalAction");
            if (originalAction.Target == null || originalAction.Target is WeakEventInfo)
                return originalAction;
            DynamicMethod weakMethodInternal = ConvertToWeakDelegate(originalAction);
            var weakEventInfo = new WeakEventInfo
                                    {
                                        Target = new WeakReference(originalAction.Target),
                                        OriginalMethod = originalAction.Method
                                    };
            if (unsubcribeDelegate != null)
                weakEventInfo.UnsubcribeDelegate = unsubcribeDelegate;

            Delegate weakDelegate = weakMethodInternal.CreateDelegate(originalAction.GetType(), weakEventInfo);
            if (unsubcribeDelegate != null)
                weakEventInfo.Delegate = weakDelegate;
            return weakDelegate;
        }

        /// <summary>
        /// Finds the weak-delegate use a original delegate in the specified array of delegate.
        /// </summary>
        /// <param name="originalAction">The original delegate to find weak delegate.</param>
        /// <param name="delegates">The specified array of delegate</param>
        /// <returns>An instance of delegate.</returns>
        public Delegate FindWeakDelegateByOriginalDelegate(Delegate originalAction, IList<Delegate> delegates)
        {
            Validate.ArgumentNotNull(originalAction, "originalAction");
            Validate.ArgumentNotNull(delegates, "delegates");
            foreach (var @delegate in delegates)
            {
                var weakEventInfo = @delegate.Target as WeakEventInfo;
                if (weakEventInfo == null) continue;
                if (weakEventInfo.Target.Target == originalAction.Target && weakEventInfo.OriginalMethod == originalAction.Method)
                    return @delegate;
            }
            return null;
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Gets a delegate to create an object.
        /// </summary>
        /// <param name="constructorInfo">
        ///     The specified <see cref="ConstructorInfo" />.
        /// </param>
        /// <param name="useCache">
        ///     If <c>true</c> using cache;otherwise <c>false</c>.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="Func{TResult,TValue}" />
        /// </returns>
        internal static ActivatorDelegate GetActivatorDelegate(ConstructorInfo constructorInfo, bool useCache)
        {
            if (!useCache)
                return
                    (ActivatorDelegate)
                    CreateActivatorMethod(constructorInfo).CreateDelegate(typeof(ActivatorDelegate));
            lock (ConstructorActivators)
            {
                var activator = ConstructorActivators[constructorInfo];
                if (activator == null)
                {
                    activator =
                        (ActivatorDelegate)
                        CreateActivatorMethod(constructorInfo).CreateDelegate(typeof(ActivatorDelegate));
                    ConstructorActivators[constructorInfo] = activator;
                }
                return activator;
            }
        }

        /// <summary>
        ///     Gets a delegate to set a value in the specified <see cref="MemberInfo" />.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="TDelegate">Type of the delegate to create.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <param name="isValueType">
        ///     If <c>true</c> is value type; otherwise <c>false</c>.
        /// </param>
        /// <param name="useCache">
        ///     If <c>true</c> using cache;otherwise <c>false</c>.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="SetterDelegate{TTarget}" />
        /// </returns>
        internal static TDelegate GetAssignMemberDelegate<TDelegate, TTarget>(MemberInfo memberInfo, bool isValueType,
                                                                              bool useCache) where TDelegate : class
        {
            if (!useCache)
                return CreateAssignMemberMethod<TTarget>(memberInfo, isValueType)
                           .CreateDelegate(typeof(TDelegate)) as TDelegate;
            CacheCollection<MemberInfo, Delegate> cache = isValueType ? MemberActivatorsRef : MemberActivators;
            lock (cache)
            {
                var @delegate = cache[memberInfo] as TDelegate;
                if (@delegate == null)
                {
                    @delegate = CreateAssignMemberMethod<TTarget>(memberInfo, isValueType)
                                    .CreateDelegate(typeof(TDelegate)) as TDelegate;
                    cache[memberInfo] = @delegate as Delegate;
                }
                return @delegate;
            }
        }

        /// <summary>
        ///     Gets a delegate to call specified <see cref="MethodInfo" />.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="TDelegate">Type of the delegate to create.</typeparam>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <param name="isValueType">
        ///     If <c>true</c> is value type; otherwise <c>false</c>.
        /// </param>
        /// <param name="useCache">
        ///     If <c>true</c> using cache;otherwise <c>false</c>.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InvokeMethodDelegate{TTarget}" />
        /// </returns>
        internal static TDelegate GetInvokeMethodDelegate<TDelegate, TTarget>(MethodInfo methodInfo,
                                                                              bool isValueType, bool useCache)
            where TDelegate : class
        {
            if (!useCache)
                return CreateMethodInvokeMethod<TTarget>(methodInfo, isValueType)
                           .CreateDelegate(typeof(TDelegate)) as TDelegate;


            CacheCollection<MemberInfo, Delegate> cache = isValueType
                                                              ? MethodActivatorsRef
                                                              : MethodActivators;
            lock (cache)
            {
                var @delegate = cache[methodInfo] as TDelegate;
                if (@delegate == null)
                {
                    @delegate = CreateMethodInvokeMethod<TTarget>(methodInfo, isValueType)
                                    .CreateDelegate(typeof(TDelegate)) as TDelegate;
                    cache[methodInfo] = @delegate as Delegate;
                }
                return @delegate;
            }
        }

        /// <summary>
        ///     Converts the Func{object[], object} to the specified delegate type.
        /// </summary>
        /// <param name="delegateType">The specified delegate type.</param>
        /// <returns>An instance of delegate type.</returns>
        internal static DynamicMethod ConvertToProxyDelegate(Type delegateType)
        {
            lock (ProxyMethods)
            {
                var dynamicMethod = ProxyMethods[delegateType];
                if (dynamicMethod == null)
                {
                    MethodInfo methodInfo = delegateType.GetMethod("Invoke");
                    bool isVoid = methodInfo.ReturnType == typeof(void);
                    ParameterInfo[] parameterInfos = InjectorUtilsInternal.GetParameterInfo(methodInfo);
                    var types = new Type[parameterInfos.Length + 1];
                    types[0] = typeof(Func<object[], object>);
                    for (int i = 0; i < parameterInfos.Length; i++)
                        types[i + 1] = parameterInfos[i].ParameterType;

                    dynamicMethod = CreateDynamicMethod(methodInfo.DeclaringType, types, methodInfo.ReturnType);
                    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                    LocalBuilder inputParamsLoc = ilGenerator.DeclareLocal(typeof(object).MakeArrayType());
                    if (!isVoid)
                        ilGenerator.DeclareLocal(methodInfo.ReturnType);

                    for (Int16 i = 0; i < parameterInfos.Length; i++)
                    {
                        ParameterInfo parameterInfo = parameterInfos[i];
                        if (!parameterInfo.IsDefined(typeof(OutAttribute), true)) continue;
                        Type elementType = parameterInfo.ParameterType.GetElementType();
                        ilGenerator.Emit(OpCodes.Ldarg, i + 1);
                        ilGenerator.Emit(OpCodes.Initobj, elementType);
                    }

                    ilGenerator.Emit(OpCodes.Ldc_I4, parameterInfos.Length);
                    ilGenerator.Emit(OpCodes.Newarr, typeof(object));
                    ilGenerator.Emit(OpCodes.Stloc_S, inputParamsLoc);

                    for (int i = 0; i < parameterInfos.Length; i++)
                    {
                        Type parameterType = parameterInfos[i].ParameterType;
                        ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                        ilGenerator.Emit(OpCodes.Ldc_I4, i);
                        ilGenerator.Emit(OpCodes.Ldarg, i + 1);
                        DereferenceIfNeed(parameterType, ilGenerator);
                        BoxIfNeed(parameterType, ilGenerator);
                        ilGenerator.Emit(OpCodes.Stelem_Ref);
                    }

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                    ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<object[], object>).GetMethod("Invoke"));
                    if (!isVoid)
                    {
                        UnboxOrCast(ilGenerator, methodInfo.ReturnType);
                        ilGenerator.Emit(OpCodes.Stloc_1);
                    }

                    /*Convert ref and out params from reference.*/
                    for (int i = 0; i < parameterInfos.Length; i++)
                    {
                        Type parameterType = parameterInfos[i].ParameterType;
                        if (!parameterType.IsByRef) continue;
                        ilGenerator.Emit(OpCodes.Ldarg_S, i + 1);
                        ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                        ilGenerator.Emit(OpCodes.Ldc_I4, i);
                        ilGenerator.Emit(OpCodes.Ldelem_Ref);
                        UnboxOrCast(ilGenerator, parameterType);
                        ReferenceIfNeed(parameterType, ilGenerator);
                    }
                    /**/
                    ilGenerator.Emit(isVoid ? OpCodes.Pop : OpCodes.Ldloc_1);
                    ilGenerator.Emit(OpCodes.Ret);

                    ProxyMethods[delegateType] = dynamicMethod;
                }
                return dynamicMethod;
            }
        }

        /// <summary>
        ///     Returns a weak-reference version of a delegate.
        /// </summary>
        /// <param name="originalAction">The delegate to convert to weak referencing.</param>
        /// <returns>A weak referencing delegate.</returns>
        internal static DynamicMethod ConvertToWeakDelegate(Delegate originalAction)
        {
#if !NETFX_CORE
            MethodInfo originalMethodDelegate = originalAction.Method;
#else
            MethodInfo originalMethodDelegate = originalAction.GetMethodInfo();
#endif

            Type delegateType = originalAction.GetType();
            var weakEventCacheItem = new WeakEventCacheItem(delegateType, originalMethodDelegate, originalAction.Target.GetType());

            lock (WeakDelegates)
            {
                var dynamicMethod = WeakDelegates[weakEventCacheItem];
                if (dynamicMethod == null)
                {
                    ParameterInfo[] parameterInfos = InjectorUtilsInternal.GetParameterInfo(originalMethodDelegate);
                    var types = new Type[parameterInfos.Length + 1];
                    types[0] = typeof(WeakEventInfo);
                    for (int i = 0; i < parameterInfos.Length; i++)
                    {
                        types[i + 1] = parameterInfos[i].ParameterType;
                    }

                    dynamicMethod = CreateDynamicMethod(originalMethodDelegate.DeclaringType, types,
                                                        originalMethodDelegate.ReturnType);
                    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                    Label originalDelegateIsNotNull = ilGenerator.DefineLabel();
                    Label returnNullLabel = ilGenerator.DefineLabel();
                    LocalBuilder declareLocal = ilGenerator.DeclareLocal(originalAction.Target.GetType());


                    for (Int16 i = 0; i < parameterInfos.Length; i++)
                    {
                        ParameterInfo parameterInfo = parameterInfos[i];
                        if (!parameterInfo.IsDefined(typeof(OutAttribute), true)) continue;
                        Type elementType = parameterInfo.ParameterType.GetElementType();
                        ilGenerator.Emit(OpCodes.Ldarg, i + 1);
                        ilGenerator.Emit(OpCodes.Initobj, elementType);
                    }

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldfld, WeakEventInfo.TargetFieldInfo);
                    ilGenerator.Emit(OpCodes.Brfalse_S, returnNullLabel);
                    //if (weakEventInfo.Target == null)
                    //   return null;

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldfld, WeakEventInfo.TargetFieldInfo);
#if NETFX_CORE
                    ilGenerator.Emit(OpCodes.Callvirt, typeof(WeakReference).GetProperty("Target").GetGetMethod(true));
#else
                    ilGenerator.Emit(OpCodes.Callvirt, typeof(WeakReference).GetProperty("Target").GetGetMethod());
#endif

                    ilGenerator.Emit(OpCodes.Castclass, declareLocal.LocalType);
                    ilGenerator.Emit(OpCodes.Stloc, declareLocal);
                    //var target = weakEventInfo.OriginalDelegate.Target;
                    ilGenerator.Emit(OpCodes.Ldloc_0);
                    ilGenerator.Emit(OpCodes.Brtrue_S, originalDelegateIsNotNull);
                    //if (target == null){

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Callvirt, WeakEventInfo.ClearMethodInfo);
                    //weakEventInfo.Clear();
                    //}
                    ilGenerator.MarkLabel(returnNullLabel);
                    if (dynamicMethod.ReturnType != typeof(void))
                    {
                        if (dynamicMethod.ReturnType.IsValueType)
                        {
                            LocalBuilder result = ilGenerator.DeclareLocal(dynamicMethod.ReturnType);
                            ilGenerator.Emit(OpCodes.Ldloca_S, result);
                            ilGenerator.Emit(OpCodes.Initobj, dynamicMethod.ReturnType);
                            ilGenerator.Emit(OpCodes.Ldloc_S, result);
                        }
                        else
                            ilGenerator.Emit(OpCodes.Ldnull);
                    }
                    ilGenerator.Emit(OpCodes.Ret);
                    //return null;
                    //}

                    ilGenerator.MarkLabel(originalDelegateIsNotNull);
                    ilGenerator.Emit(OpCodes.Ldloc_0);
                    for (Int16 i = 1; i < types.Length; i++)
                    {
                        ilGenerator.Emit(OpCodes.Ldarg_S, i);
                    }
                    ilGenerator.Emit(OpCodes.Callvirt, originalMethodDelegate);
                    ilGenerator.Emit(OpCodes.Ret);
                    //return target.Invoke(arg1, arg2, .....);

                    WeakDelegates[weakEventCacheItem] = dynamicMethod;
                }
                return dynamicMethod;
            }
        }

        internal static void DereferenceIfNeed(Type type, ILGenerator ilGenerator)
        {
            if (!type.IsByRef) return;
            type = type.GetElementType();
#if NETFX_CORE
            if (type.GetTypeInfo().IsValueType)
#else
            if (type.IsValueType)
#endif
            {
#if NETFX_CORE
                if (!type.GetTypeInfo().IsPrimitive)
#else
                if (!type.IsPrimitive)
#endif
                {
                    ilGenerator.Emit(OpCodes.Ldobj, type);
                    return;
                }
                if (type == typeof(sbyte))
                {
                    ilGenerator.Emit(OpCodes.Ldind_I1);
                    return;
                }
                if (type == typeof(byte))
                {
                    ilGenerator.Emit(OpCodes.Ldind_U1);
                    return;
                }
                if (type == typeof(char))
                {
                    ilGenerator.Emit(OpCodes.Ldind_U2);
                    return;
                }
                if (type == typeof(float))
                {
                    ilGenerator.Emit(OpCodes.Ldind_R4);
                    return;
                }
                if (type == typeof(float))
                {
                    ilGenerator.Emit(OpCodes.Ldind_R4);
                    return;
                }
                if (type == typeof(double))
                {
                    ilGenerator.Emit(OpCodes.Ldind_R8);
                    return;
                }
                if (type == typeof(ushort))
                {
                    ilGenerator.Emit(OpCodes.Ldind_U2);
                    return;
                }
                if (type == typeof(short))
                {
                    ilGenerator.Emit(OpCodes.Ldind_I2);
                    return;
                }
                if (type == typeof(uint))
                {
                    ilGenerator.Emit(OpCodes.Ldind_U4);
                    return;
                }
                if (type == typeof(int))
                {
                    ilGenerator.Emit(OpCodes.Ldind_I4);
                    return;
                }
                if (type == typeof(ulong))
                {
                    ilGenerator.Emit(OpCodes.Ldind_I8);
                    return;
                }
                if (type == typeof(long))
                {
                    ilGenerator.Emit(OpCodes.Ldind_I8);
                    return;
                }
                if (type == typeof(bool))
                {
                    ilGenerator.Emit(OpCodes.Ldind_I1);
                    return;
                }
            }
            ilGenerator.Emit(OpCodes.Ldind_Ref);
        }

        internal static void ReferenceIfNeed(Type type, ILGenerator ilGenerator)
        {
            if (!type.IsByRef) return;
            type = type.GetElementType();
#if NETFX_CORE
            if (!type.GetTypeInfo().IsValueType)
#else
            if (!type.IsValueType)
#endif
            {
                ilGenerator.Emit(OpCodes.Stind_Ref);
                return;
            }

#if NETFX_CORE
            if (!type.GetTypeInfo().IsPrimitive)
#else
            if (!type.IsPrimitive)
#endif
            {
                ilGenerator.Emit(OpCodes.Stobj, type);
                return;
            }
            if (type == typeof(sbyte))
            {
                ilGenerator.Emit(OpCodes.Stind_I1);
                return;
            }
            if (type == typeof(byte))
            {
                ilGenerator.Emit(OpCodes.Stind_I1);
                return;
            }
            if (type == typeof(char))
            {
                ilGenerator.Emit(OpCodes.Stind_I2);
                return;
            }
            if (type == typeof(float))
            {
                ilGenerator.Emit(OpCodes.Stind_R4);
                return;
            }
            if (type == typeof(float))
            {
                ilGenerator.Emit(OpCodes.Stind_R4);
                return;
            }
            if (type == typeof(double))
            {
                ilGenerator.Emit(OpCodes.Stind_R8);
                return;
            }
            if (type == typeof(ushort))
            {
                ilGenerator.Emit(OpCodes.Stind_I2);
                return;
            }
            if (type == typeof(short))
            {
                ilGenerator.Emit(OpCodes.Stind_I2);
                return;
            }
            if (type == typeof(uint))
            {
                ilGenerator.Emit(OpCodes.Stind_I4);
                return;
            }
            if (type == typeof(int))
            {
                ilGenerator.Emit(OpCodes.Stind_I4);
                return;
            }
            if (type == typeof(ulong))
            {
                ilGenerator.Emit(OpCodes.Stind_I8);
                return;
            }
            if (type == typeof(long))
            {
                ilGenerator.Emit(OpCodes.Stind_I8);
                return;
            }
            if (type == typeof(bool))
            {
                ilGenerator.Emit(OpCodes.Stind_I1);
                return;
            }
        }

        internal static void BoxIfNeed(Type type, ILGenerator ilGenerator)
        {
            Type elementType = null;
            if (type.IsByRef)
                elementType = type.GetElementType();
            if (elementType == null)
                elementType = type;
            if (elementType.IsValueType)
                ilGenerator.Emit(OpCodes.Box, elementType);
        }

        /// <summary>
        ///     Unbox or cast specified type.
        /// </summary>
        internal static void UnboxOrCast(ILGenerator il, Type type)
        {
            Type elementType = null;
            if (type.IsByRef)
                elementType = type.GetElementType();
            if (elementType == null)
                elementType = type;
            il.Emit(elementType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, elementType);
        }

        #region Private members

        private static DynamicMethod CreateActivatorMethod(ConstructorInfo constructorInfo)
        {
            Type declaringType = constructorInfo.DeclaringType;
            DynamicMethod dynamicMethod = CreateDynamicMethod(declaringType, new[] { typeof(object[]) },
                                                              typeof(object));
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            LoadMethodArguments(ilGenerator, constructorInfo);
            ilGenerator.Emit(OpCodes.Newobj, constructorInfo);
#if NETFX_CORE
            if (declaringType.GetTypeInfo().IsValueType)
#else
            if (declaringType.IsValueType)
#endif
                ilGenerator.Emit(OpCodes.Box, declaringType);
            ilGenerator.Emit(OpCodes.Ret);
            return dynamicMethod;
        }

        private static DynamicMethod CreateGetterMemberMethod<TTarget>(MemberInfo memberInfo, Type outputType)
        {
            Type paramType;
            MethodInfo propertyMethod = null;
            bool isStatic;
            Type declaringType = memberInfo.DeclaringType;

#if !NETFX_CORE
            switch (memberInfo.MemberType)
#else
            switch (memberInfo.GetMemberType())
#endif
            {
                case MemberTypes.Field:
                    var fieldInfo = (FieldInfo)memberInfo;
                    paramType = fieldInfo.FieldType;
                    isStatic = fieldInfo.IsStatic;
                    break;
                case MemberTypes.Property:
                    var propertyInfo = (PropertyInfo)memberInfo;
                    paramType = propertyInfo.PropertyType;
                    propertyMethod = propertyInfo.GetGetMethod(true);
                    if (propertyMethod == null)
                        throw new ArgumentException(string.Format("The get-method not found in {0}.",
                                                                  InjectorUtilsInternal.FormatMember(propertyInfo, null)), "memberInfo");
                    isStatic = propertyMethod.IsStatic;
                    break;
                default:
                    throw new ArgumentException("The method can only create a get-methods for properties and fields.",
                                                "memberInfo");
            }

            DynamicMethod getter = CreateDynamicMethod(declaringType, new[] { typeof(TTarget) }, outputType);
            ILGenerator generator = getter.GetILGenerator();
            if (!isStatic)
            {
#if !NETFX_CORE
                if (typeof(TTarget).IsValueType)
#else
                if (typeof(TTarget).GetTypeInfo().IsValueType)
#endif
                    generator.Emit(OpCodes.Ldarga_S, 0);
                else
                    generator.Emit(OpCodes.Ldarg_0);
                if (declaringType != typeof(TTarget))
                    UnboxOrCast(generator, declaringType);
            }
            if (propertyMethod != null)
#if !NETFX_CORE
                generator.Emit(declaringType.IsValueType || isStatic ? OpCodes.Call : OpCodes.Callvirt,
                               propertyMethod);
#else
                generator.Emit(declaringType.GetTypeInfo().IsValueType || isStatic ? OpCodes.Call : OpCodes.Callvirt,
                               propertyMethod);
#endif
            else
                generator.Emit(isStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, (FieldInfo)memberInfo);
            if (paramType != outputType)
                BoxIfNeed(paramType, generator);
            generator.Emit(OpCodes.Ret);
            return getter;
        }

        private static DynamicMethod CreateAssignMemberMethod<T>(MemberInfo memberInfo, bool isRef)
        {
            Type paramTargetType = typeof(T);
            Type declaringType = memberInfo.DeclaringType;

#if NETFX_CORE
            var isTvalue = paramTargetType.GetTypeInfo().IsValueType;
            var isValueDeclaring = memberInfo.DeclaringType.GetTypeInfo().IsValueType;
#else
            bool isTvalue = paramTargetType.IsValueType;
            bool isValueDeclaring = declaringType.IsValueType;
#endif
            if (!isRef && isValueDeclaring && !isTvalue)
                throw new InvalidOperationException(
                    "It is impossible to assign a field/property to value type passed is not a reference. Use the version of the method to pass a parameter by reference.");
            bool isValue = isValueDeclaring && !isTvalue;
            Type paramType;
            MethodInfo propertyMethod = null;
            bool isStatic;
#if NETFX_CORE
            switch (memberInfo.GetMemberType())
#else
            switch (memberInfo.MemberType)
#endif
            {
                case MemberTypes.Field:
                    var fieldInfo = (FieldInfo)memberInfo;
                    paramType = fieldInfo.FieldType;
                    isStatic = fieldInfo.IsStatic;
                    break;
                case MemberTypes.Property:
                    var propertyInfo = (PropertyInfo)memberInfo;
                    paramType = propertyInfo.PropertyType;
                    propertyMethod = propertyInfo.GetSetMethod(true);
                    if (propertyMethod == null)
                        throw new ArgumentException(string.Format("The set-method not found in {0}.",
                                                                  InjectorUtilsInternal.FormatMember(propertyInfo, null)), "memberInfo");
                    isStatic = propertyMethod.IsStatic;
                    break;
                default:
                    throw new ArgumentException("The method can only create set-method for properties and fields.",
                                                "memberInfo");
            }
            Type targetType = paramTargetType;
            if (isRef)
                targetType = targetType.MakeByRefType();
            DynamicMethod setter = CreateDynamicMethod(declaringType, new[] { targetType, typeof(object) }, typeof(void));

            ILGenerator generator = setter.GetILGenerator();
            if (!isStatic)
            {
                generator.Emit(OpCodes.Ldarg_0);
                if (isRef && !isTvalue)
                    generator.Emit(OpCodes.Ldind_Ref);
                if (paramTargetType != declaringType)
                {
                    UnboxOrCast(generator, declaringType);
                    if (isValue)
                    {
                        LocalBuilder declareLocal = generator.DeclareLocal(declaringType);
                        generator.Emit(OpCodes.Stloc_0);
                        generator.Emit(OpCodes.Ldloca_S, declareLocal);
                    }
                }
            }
            generator.Emit(OpCodes.Ldarg_1);
            UnboxOrCast(generator, paramType);
            if (propertyMethod != null)
                generator.Emit(isValueDeclaring || isStatic ? OpCodes.Call : OpCodes.Callvirt, propertyMethod);
            else
                generator.Emit(isStatic ? OpCodes.Stsfld : OpCodes.Stfld, (FieldInfo)memberInfo);
            if (isValue)
            {
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldloc_0);
                generator.Emit(OpCodes.Box, declaringType);
                generator.Emit(OpCodes.Stind_Ref);
            }
            generator.Emit(OpCodes.Ret);
            return setter;
        }

        private static DynamicMethod CreateMethodInvokeMethod<T>(MethodInfo method, bool isRef)
        {
            Type paramTargetType = typeof(T);
            Type declaringType = method.DeclaringType;
            Action saveValueTypeIfNeed = () => { };
#if NETFX_CORE
            var isTvalue = paramTargetType.GetTypeInfo().IsValueType;
            var isValueDeclaring = declaringType.GetTypeInfo().IsValueType;
#else
            bool isTvalue = paramTargetType.IsValueType;
            bool isValueDeclaring = declaringType.IsValueType;
#endif
            if (!isRef && isValueDeclaring && !isTvalue)
                throw new InvalidOperationException(
                    "It is impossible to call method to value type passed is not a reference. Use the version of the method to pass a parameter by reference.");
            bool isValue = isValueDeclaring && !isTvalue;
            bool isVoid = method.ReturnType == typeof(void);
            Type targetType = paramTargetType;
            if (isRef)
                targetType = targetType.MakeByRefType();
            DynamicMethod dynamicMethod = CreateDynamicMethod(declaringType,
                                                              new[] { targetType, typeof(object[]) },
                                                              typeof(object));
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            if (!method.IsStatic)
            {
                ilGenerator.Emit(OpCodes.Ldarg_0);
                if (isRef && !isTvalue)
                    ilGenerator.Emit(OpCodes.Ldind_Ref);
                if (paramTargetType != declaringType)
                {
                    UnboxOrCast(ilGenerator, declaringType);
                    if (isValue)
                    {
                        LocalBuilder declareLocal = ilGenerator.DeclareLocal(declaringType);
                        ilGenerator.Emit(OpCodes.Stloc_S, declareLocal);
                        ilGenerator.Emit(OpCodes.Ldloca_S, declareLocal);
                        saveValueTypeIfNeed = () =>
                                                  {
                                                      ilGenerator.Emit(OpCodes.Ldarg_0);
                                                      ilGenerator.Emit(OpCodes.Ldloc_S, declareLocal);
                                                      ilGenerator.Emit(OpCodes.Box, declaringType);
                                                      ilGenerator.Emit(OpCodes.Stind_Ref);
                                                  };
                    }
                }
            }
            Action action = LoadMethodArguments(ilGenerator, method);
            ilGenerator.Emit(isValueDeclaring || method.IsStatic ? OpCodes.Call : OpCodes.Callvirt, method);
            saveValueTypeIfNeed();
            action();
            if (isVoid)
                ilGenerator.Emit(OpCodes.Ldnull);
            else
            {
#if NETFX_CORE
                if (method.ReturnType.GetTypeInfo().IsValueType)
#else
                if (method.ReturnType.IsValueType)
#endif
                    ilGenerator.Emit(OpCodes.Box, method.ReturnType);
            }
            ilGenerator.Emit(OpCodes.Ret);
            return dynamicMethod;
        }

        /// <summary>
        ///     Loads method argument in IL.
        /// </summary>
        private static Action LoadMethodArguments(ILGenerator il, MethodBase targetMethod)
        {
            var dictionary = new Dictionary<LocalBuilder, int>();
            ParameterInfo[] parameters = targetMethod.GetParameters();
            OpCode ldargOpcode = targetMethod is ConstructorInfo ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1;
            for (int idx = 0; idx < parameters.Length; idx++)
            {
                ParameterInfo parameterInfo = parameters[idx];
                il.Emit(ldargOpcode);
                il.Emit(OpCodes.Ldc_I4, idx);
                if (parameterInfo.ParameterType.IsByRef)
                {
                    Type elementType = parameterInfo.ParameterType.GetElementType();
                    LocalBuilder local = il.DeclareLocal(elementType);
                    il.Emit(OpCodes.Ldelem_Ref);
                    UnboxOrCast(il, elementType);
                    il.Emit(OpCodes.Stloc_S, local);
                    il.Emit(OpCodes.Ldloca_S, local);
                    dictionary.Add(local, idx);
                }
                else
                {
                    il.Emit(OpCodes.Ldelem_Ref);
                    UnboxOrCast(il, parameterInfo.ParameterType);
                }
            }
            return () =>
                       {
                           foreach (var i in dictionary)
                           {
                               il.Emit(ldargOpcode);
                               il.Emit(OpCodes.Ldc_I4, i.Value);
                               il.Emit(OpCodes.Ldloc_S, i.Key);
#if NETFX_CORE
                               if (i.Key.LocalType.GetTypeInfo().IsValueType)
#else
                               if (i.Key.LocalType.IsValueType)
#endif
                                   il.Emit(OpCodes.Box, i.Key.LocalType);
                               il.Emit(OpCodes.Stelem_Ref);
                           }
                       };
        }

        /// <summary>
        ///     Creates dynamic method with skip visibility.
        /// </summary>
        private static DynamicMethod CreateDynamicMethod(Type type, Type[] inputValue, Type outputValue)
        {
#if NET
#if NETFX_CORE
            var typeInfo = type.GetTypeInfo();
#else
            Type typeInfo = type;
#endif
            return
                new DynamicMethod(
                    string.Format("_DynamicMethodMugenInjection_{0}_{1}", type.Name, Guid.NewGuid().ToString("N")), outputValue,
                    inputValue, typeInfo.Module, true);
#else
            return new DynamicMethod(string.Format("_DynamicMethodMugenInjection_{0}_{1}", type.Name, Guid.NewGuid().ToString("N")), outputValue,
                                                 inputValue);
#endif
        }

        #endregion

        #endregion
    }
}